package com.byron.media.server.client;

import com.byron.media.server.config.MediaServerConfig;
import com.byron.media.server.handlers.MediaDecoder;
import com.byron.media.server.handlers.MediaEncoder;
import com.byron.media.server.sessions.RealSession;
import com.byron.media.server.sessions.SessionContainer;
import com.byron.media.server.model.*;
import com.byron.media.server.utils.JsonUtils;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;

@Slf4j
public class MediaClient {

    private boolean autoReconnect = true;

    private EventLoopGroup group;

    private ChannelFuture connectFuture = null;

    private MediaClientConfig mediaClientConfig;

    private boolean connected;          // 是否已连接

    private MediaClientConfig config;

    public MediaClient(EventLoopGroup group, MediaClientConfig config) {
        this.config = config;
        this.group = group;
        this.mediaClientConfig = config;
    }

    public void start(){
        new Thread(() -> connect()).start();
    }

    private void connect(){

        Bootstrap bootstrap = new Bootstrap();
        try {

            bootstrap.group(group) // 注册线程池
//                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_RCVBUF, 5 * 1024 * 1024)
                    .option(ChannelOption.SO_SNDBUF, 5 * 1024 * 1024)

//                    .option(ChannelOption.SO_RCVBUF, 200 * 1024)
//                    .option(ChannelOption.SO_SNDBUF, 200 * 1024)

//                    .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1, 1024 * 1024 * 8))
                    .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(32 * 1024, 64 * 1024))
                    .option(ChannelOption.MAX_MESSAGES_PER_READ, Integer.MAX_VALUE)
                    .channel(NioSocketChannel.class) // 使用NioSocketChannel来作为连接用的channel类
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
//                                    .addLast("chunkedWriteHandler", new ChunkedWriteHandler())
                                    .addLast(new MediaDecoder(), new MediaEncoder());
                            if(mediaClientConfig.getHeartTimeout() > 0){
                                ch.pipeline().addLast(new MediaClientHeartBeat(config, mediaClientConfig.getHeartTimeout()));
                            }
                            ch.pipeline().addLast(new RealSession());
                            ch.pipeline().addLast(new MediaClientHandler(config));
                        }
                    });
            connectFuture = bootstrap.connect(
                    new InetSocketAddress(
                            mediaClientConfig.getServerAddress(),
                            mediaClientConfig.getServerPort()));
            connectFuture.sync();
            if(MediaServerConfig.getInstance().isLog()){
                log.info("已连接上主服务器" + mediaClientConfig.getServerAddress() + ":" + mediaClientConfig.getServerPort());
            }
            connected = true;
            connectFuture.channel().closeFuture().sync();
            connected = false;

            // TODO 离线的时候会自动重连，但是这时候就需要避免发送数据

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            if(autoReconnect){
                new Thread(() -> {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    connect();
                }).start();
            } else {
                group.shutdownGracefully();
            }
        }
    }

    /**
     * 发送数据
     * @param message
     */
    public void send(Object message){
        if(!connected){
            return;
        }
        if(connectFuture != null){
            connectFuture.channel().writeAndFlush(message);
        }
    }

    /**
     * 发送命令
     * @param header
     */
    public void send(MediaHeaderObj header) {
        // TODO 如果是注册过程中代理服务器掉线了，应该如何处理？
        if(!connected){
            return;
        }
        String json = JsonUtils.toJson(header);
        if(connectFuture != null){
            connectFuture.channel().writeAndFlush(json);
        }
    }

    /**
     * 关闭连接
     */
    public void close(){
        autoReconnect = false;
        connectFuture.channel().close();
    }
}
